﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using JSON;
using System.Web.Configuration;
using System.Web;
using Facebook_Graph_Toolkit.FacebookObjects;
using System.Net;
using System.Drawing;
using System.IO;
using Facebook_Graph_Toolkit.Helpers;
using System.Collections;

namespace Facebook_Graph_Toolkit.GraphApi {
    /// <summary>
    /// Stores the current Access Token and provides methods to make Api calls using this Access Token
    /// </summary>
    [Serializable]
    public class Api {
   
        #region public properties
        /// <summary>
        /// Access Token of the current session. Used to make Api calls.
        /// </summary>
        public string AccessToken {
            get ; private set;
        }

        /// <summary>
        /// The DateTime, in UTC, in which the access_token expires
        /// </summary>
        public DateTime Expires {
            get;
            private set;
        }

        #endregion

        #region constructors

        internal Api(string AccessToken, DateTime Expires) {
            this.AccessToken = AccessToken;
            this.Expires = Expires;
            //string[] b = AccessToken.Split('|')[1].Split('-');
            //this.UserID = UserID;
        }

        /// <summary>
        /// Constructor of new Api object. Use this if you already have an Access Token.
        /// </summary>
        /// <param name="AccessToken">Access Token given by Facebook</param>
        public Api(string AccessToken) {
            this.AccessToken = AccessToken;
            //this.UserID = UserID;
        }
        #endregion

        #region public
        /// <summary>
        /// Get the URL of the picture of a Facebook object
        /// </summary>
        /// <param name="ID"></param>
        /// <returns>The URL of picture</returns>
        public string GetPictureURL(string ID) {
            return Helpers.WebResponseHelper.GetWebResponseRedirectURL(String.Format("https://graph.facebook.com/{0}/picture?access_token={1}", ID, AccessToken));
        }

        /// <summary>
        /// Performs a FQL query and return the result
        /// </summary>
        /// <param name="FqlQuery">FQL query</param>
        /// <returns>query result</returns>
        public JsonArray Fql(string FqlQuery) {
            string url = string.Format("https://api.facebook.com/method/fql.query?query={0}&format=JSON&access_token={1}", HttpUtility.UrlEncode(FqlQuery), AccessToken);
            return new JsonArray(Helpers.WebResponseHelper.GetWebResponse(url));
        }
        /// <summary>
        /// Performs multiple FQL queries and return the results
        /// </summary>
        /// <param name="Queries">Queries to be executed</param>
        /// <returns></returns>
        public IDictionary<string,JsonArray> FqlMultiQuery(KeyValuePair<string, string>[] Queries) {
            int DistinctCount = (from kvp in Queries
                                 select kvp.Key).Distinct().Count();

            if (DistinctCount < Queries.Length) throw new ArgumentException("The keys of queries must be distinct.");

            string s = "https://graph.facebook.com/fql?q={";
            for (int i = 0; i < Queries.Length; i++) {
                s += string.Format("\"{0}\":\"{1}\"", Queries[i].Key, Queries[i].Value);
                if (i != Queries.Length - 1) s += ",";
            }
            s += "}&access_token=" + AccessToken;

            JsonObject JO = new JsonObject(Helpers.WebResponseHelper.GetWebResponse(s));
            var Q = (from JsonObject J in (JsonArray)JO["data"]
                     select J).ToDictionary(v => (string)v["name"], v => (JsonArray)v["fql_result_set"]);
            return Q;
        }

        /// <summary>
        /// Performs a FQL query that does not require an access token and obtain the result
        /// </summary>
        /// <param name="FqlQuery">FQL query</param>
        /// <returns>query result</returns>
        public static JsonArray FqlPublic(string FqlQuery) {
            string url = string.Format("https://api.facebook.com/method/fql.query?query={0}&format=JSON", HttpUtility.UrlEncode(FqlQuery));
            return new JsonArray(Helpers.WebResponseHelper.GetWebResponse(url));
        }

        #endregion

        #region Publish

        #region Events
        /// <summary>
        /// Creates an event on Facebook
        /// </summary>
        /// <param name="EventName">Event title</param>
        /// <param name="Location">Location of Event</param>
        /// <param name="StartTime">Start Time of Event, in UTC</param>
        /// <param name="EndTime">End Time of Event, in UTC</param>
        /// <returns>Event ID</returns>
        public string CreateEvent(string EventName, string Location, DateTime StartTime, DateTime EndTime) {
            string response = WebResponseHelper.UploadString("https://graph.facebook.com/me/events", string.Format("access_token={0}&name={1}&start_time={2}&end_time={3}&location={4}", AccessToken, EventName, Helpers.Common.ToUnixTimestamp(StartTime), Helpers.Common.ToUnixTimestamp(EndTime), Location));
            JsonObject JO = new JsonObject(response);
            return (string)JO["id"];
        }

        /// <summary>
        /// Creates an event on Facebook with an async call
        /// </summary>
        /// <param name="EventName">Event title</param>
        /// <param name="Location">Location of Event</param>
        /// <param name="StartTime">Start Time of Event, in UTC</param>
        /// <param name="EndTime">End Time of Event, in UTC</param>
        public void CreateEventAsync(string EventName, string Location, DateTime StartTime, DateTime EndTime) {
            using (WebClient client = new WebClient()) {
                client.UploadStringAsync(new Uri("https://graph.facebook.com/me/events"), string.Format("access_token={0}&name={1}&start_time={2}&end_time={3}&location={4}", AccessToken, EventName, Helpers.Common.ToUnixTimestamp(StartTime), Helpers.Common.ToUnixTimestamp(EndTime), Location));
            }
        }

        /// <summary>
        /// Edits the location of an Event on Facebook
        /// </summary>
        /// <param name="EventID">Event ID</param>
        /// <param name="Location">new location of Event</param>
        public void EditEventLocationAsync(string EventID, string Location) {
            using (WebClient client = new WebClient()) {
                client.UploadStringAsync(new Uri(string.Format("https://graph.facebook.com/{0}", EventID)), string.Format("access_token={0}&location={1}", AccessToken, Location));
            }
        }

        /// <summary>
        /// Rsvp an Event on behalf of the user
        /// </summary>
        /// <param name="EventID">Event ID</param>
        /// <param name="EventRsvp">Rsvp (attending / maybe / declined)</param>
        public void RsvpEventAsync(string EventID, Rsvp EventRsvp) {
            string rsvp_string = "";
            switch (EventRsvp) {
                case Rsvp.Attending:
                    rsvp_string = "attending";
                    break;
                case Rsvp.Maybe:
                    rsvp_string = "maybe";
                    break;
                case Rsvp.Declined:
                    rsvp_string = "declined";
                    break;
                default:
                    throw new ArgumentOutOfRangeException("EventRsvp", "\"Not Replied\" is not a valid Rsvp");
            }
            WebClient client = new WebClient();
            client.UploadStringAsync(new Uri(string.Format("https://graph.facebook.com/{0}/{1}", EventID,rsvp_string)), string.Format("access_token={0}", AccessToken));
            client.Dispose();
        }
        #endregion

        #region Comments and Likes
        /// <summary>
        /// Post a comment to a certain post on Facebook
        /// </summary>
        /// <param name="PostID">Post ID</param>
        /// <param name="message">Comment to be posted</param>
        /// <returns>Comment ID</returns>
        public string PostComment(string PostID, string message) {
            string response = WebResponseHelper.UploadString(string.Format("https://graph.facebook.com/{0}/comments", PostID), string.Format("access_token={0}&message={1}", AccessToken, HttpUtility.UrlEncode(message)));
            JsonObject JO = new JsonObject(response);
            return (string)JO["id"];
        }

        /// <summary>
        /// Delete an object on Facebok. The object must be created by the application.
        /// </summary>
        /// <param name="ID">ID of object</param>
        public void DeleteObject(string ID) {
            using (WebClient client = new WebClient()) {
                client.UploadString(string.Format("https://graph.facebook.com/{0}", ID), "DELETE", string.Format("access_token={0}", AccessToken));
            }
        }

        /// <summary>
        /// Like a certain post / comment on Facebook
        /// </summary>
        /// <param name="ID">Post ID / Comment ID</param>
        public void PostLike(string ID) {
            WebResponseHelper.UploadString(string.Format("https://graph.facebook.com/{0}/likes", ID), string.Format("access_token={0}", AccessToken));
        }

        /// <summary>
        /// Delete a like on a certain post / comment on Facebook
        /// </summary>
        /// <param name="ID">Post ID / Comment ID</param>
        public void DeleteLike(string ID) {
            using (WebClient client = new WebClient()) {
                client.UploadString(string.Format("https://graph.facebook.com/{0}/likes", ID), "DELETE", string.Format("access_token={0}", AccessToken));
            }
        }
        #endregion

        #region Feed


        /// <summary>
        /// Posts a message to the user's wall
        /// </summary>
        /// <param name="message">Message to be posted</param>
        /// <returns>Post ID</returns>
        public string PostFeed(string message) {
            return PostFeedToTarget("me", message);
        }

        /// <summary>
        /// Posts a message to the user's wall with an async method
        /// </summary>
        /// <param name="message">Message to be posted</param>
        public void PostFeedAsync(string message) {
            PostFeedToTargetAsync("me", message);
        }

        /// <summary>
        /// Post a message to the wall of a Page or user's friend
        /// </summary>
        /// <param name="ID">User ID / Page ID</param>
        /// <param name="message">Message to be posted</param>
        /// <returns>Post ID</returns>
        public string PostFeedToTarget(string ID, string message) {
            string response = WebResponseHelper.UploadString(string.Format("https://graph.facebook.com/{0}/feed", ID), string.Format("access_token={0}&message={1}", AccessToken, HttpUtility.UrlEncode(message)));
            JsonObject JO = new JsonObject(response);
            return (string)JO["id"];
        }

        /// <summary>
        /// Post a message to the wall of a Page or user's friend via an async method
        /// </summary>
        /// <param name="ID">User ID / Page ID</param>
        /// <param name="message">Message to be posted</param>
        public void PostFeedToTargetAsync(string ID, string message) {
            using (WebClient client = new WebClient()) {
                client.UploadStringAsync(new Uri(string.Format("https://graph.facebook.com/{0}/feed", ID)), string.Format("access_token={0}&message={1}", AccessToken, HttpUtility.UrlEncode(message)));
            }
        }

        /// <summary>
        /// Post a message with link attachment to the user's wall
        /// </summary>
        /// <param name="message">The message</param>
        /// <param name="picture">If available, a URL to the picture included with this post.</param>
        /// <param name="link">The link attached to this post</param>
        /// <param name="action">An action link that appears next to "Comment" and "Like".</param>
        /// <returns>Post ID</returns>
        public string PostFeed(string message, string picture, LinkAttachment link, PostAction action) {
            return PostFeedToTarget("me", message, picture, link, action);
        }

        /// <summary>
        /// Post a message with link attachment to the user's wall with an async method
        /// </summary>
        /// <param name="message">The message</param>
        /// <param name="picture">If available, a URL to the picture included with this post.</param>
        /// <param name="link">The link attached to this post</param>
        /// <param name="action">An action link that appears next to "Comment" and "Like".</param>
        public void PostFeedAsync(string message, string picture, LinkAttachment link, PostAction action) {
            PostFeedToTargetAsync("me", message, picture, link, action);
        }

        /// <summary>
        /// Post a message with link attachment to the wall of a Page or user's friend
        /// </summary>
        /// <param name="ID">User ID / Page ID</param>
        /// <param name="message">The message</param>
        /// <param name="picture">If available, a URL to the picture included with this post.</param>
        /// <param name="link">The link attached to this post</param>
        /// <param name="action">An action link that appears next to "Comment" and "Like".</param>
        /// <returns>Post ID</returns>
        public string PostFeedToTarget(string ID, string message, string picture, LinkAttachment link, PostAction action) {
            IDictionary<string, string> parameters = new Dictionary<string, string>();
            parameters.Add("access_token", AccessToken);
            parameters.Add("message", HttpUtility.UrlEncode(message));
            parameters.Add("picture", picture);
            if (link != null) {
                parameters.Add("link", link.URL);
                parameters.Add("name", HttpUtility.UrlEncode(link.Name));
                parameters.Add("caption", HttpUtility.UrlEncode(link.Caption));
                parameters.Add("description", HttpUtility.UrlEncode(link.Description));
            }
            if (action != null) {
                parameters.Add("actions", "{\"name\":\"" + action.Name + "\",\"link\":\"" + action.Link + "\"}");
            }

            string response = WebResponseHelper.UploadString(string.Format("https://graph.facebook.com/{0}/feed", ID), Helpers.Common.BuildQueryString(parameters));
            JsonObject JO = new JsonObject(response);
            return (string)JO["id"];
        }

        /// <summary>
        /// Post a message with link attachment to the wall of a Page or user's friend with an async method
        /// </summary>
        /// <param name="ID">User ID / Page ID</param>
        /// <param name="message">The message</param>
        /// <param name="picture">If available, a URL to the picture included with this post.</param>
        /// <param name="link">The link attached to this post</param>
        /// <param name="action">An action link that appears next to "Comment" and "Like".</param>
        public void PostFeedToTargetAsync(string ID, string message, string picture, LinkAttachment link, PostAction action) {
            IDictionary<string, string> parameters = new Dictionary<string, string>();
            parameters.Add("access_token", AccessToken);
            parameters.Add("message", HttpUtility.UrlEncode(message));
            parameters.Add("picture", picture);
            if (link != null) {
                parameters.Add("link", link.URL);
                parameters.Add("name", HttpUtility.UrlEncode(link.Name));
                parameters.Add("caption", HttpUtility.UrlEncode(link.Caption));
                parameters.Add("description", HttpUtility.UrlEncode(link.Description));
            }
            if (action != null) {
                parameters.Add("actions", "{\"name\":\"" + action.Name + "\",\"link\":\"" + action.Link + "\"}");
            }

            using (WebClient client = new WebClient()) {
                client.UploadStringAsync(new Uri(string.Format("https://graph.facebook.com/{0}/feed", ID)), Helpers.Common.BuildQueryString(parameters));
            }
        }
        #endregion

        #region Photo
        /// <summary>
        /// Publishes a photo to the app's album and post it on the user's wall
        /// </summary>
        /// <param name="photo">photo to be published</param>
        /// <param name="message">message with this photo</param>
        /// <returns>PhotoID of the photo published</returns>
        public string PublishPhoto(Bitmap photo, string message) {
            return PublishPhoto("me", photo, message);
        }

        /// <summary>
        /// Publishes a photo to a specific album and post it on the user's wall
        /// </summary>
        /// <param name="AlbumID">AlbumID of photo album which the photo will be published to</param>
        /// <param name="photo">photo to be published</param>
        /// <param name="message">message with this photo</param>
        /// <returns>PhotoID of the photo published</returns>
        public string PublishPhoto(string AlbumID, Bitmap photo,string message) {
            MemoryStream MS = new MemoryStream();
            photo.Save(MS, System.Drawing.Imaging.ImageFormat.Jpeg);
            byte[] Imagebytes = MS.ToArray();
            MS.Dispose();

            //Set up basic variables for constructing the multipart/form-data data
            string newline = "\r\n";
            string boundary = DateTime.Now.Ticks.ToString("x");
            string data = "";

            //Construct data
            data += "--" + boundary + newline;
            data += "Content-Disposition: form-data; name=\"message\"" + newline + newline;
            data += message + newline;

            data += "--" + boundary + newline;
            data += "Content-Disposition: form-data; filename=\"test.jpg\"" + newline;
            data += "Content-Type: image/jpeg" + newline + newline;

            string ending = newline + "--" + boundary + "--" + newline;

            //Convert data to byte[] array
            MemoryStream finaldatastream = new MemoryStream();
            byte[] databytes = Encoding.UTF8.GetBytes(data);
            byte[] endingbytes = Encoding.UTF8.GetBytes(ending);
            finaldatastream.Write(databytes, 0, databytes.Length);
            finaldatastream.Write(Imagebytes, 0, Imagebytes.Length);
            finaldatastream.Write(endingbytes, 0, endingbytes.Length);
            byte[] finaldatabytes = finaldatastream.ToArray();
            finaldatastream.Dispose();

            //Make the request
            WebRequest request = HttpWebRequest.Create(string.Format("https://graph.facebook.com/{0}/photos?access_token={1}",AlbumID,AccessToken));
            request.ContentType = "multipart/form-data; boundary=" + boundary;
            request.ContentLength = finaldatabytes.Length;
            request.Method = "POST";
            using (Stream RStream = request.GetRequestStream()) {
                RStream.Write(finaldatabytes, 0, finaldatabytes.Length);
            }
            WebResponse WR = request.GetResponse();
            string _Response = "";
            using (StreamReader sr = new StreamReader(WR.GetResponseStream())) {
                _Response = sr.ReadToEnd();
            }
            JsonObject JO = new JsonObject(_Response);
            return (string)JO["id"];
        }

        /// <summary>
        /// Publishes a photo to the app's album and post it on the user's wall
        /// </summary>
        /// <param name="photo">photo to be published</param>
        /// <param name="message">message with this photo</param>
        public void PublishPhotoAsync(Bitmap photo, string message) {
            PublishPhotoAsync("me", photo, message);
        }

        /// <summary>
        /// Publishes a photo to a specific album and post it on the user's wall
        /// </summary>
        /// <param name="AlbumID">AlbumID of photo album which the photo will be published to</param>
        /// <param name="photo">photo to be published</param>
        /// <param name="message">message with this photo</param>
        public void PublishPhotoAsync(string AlbumID, Bitmap photo, string message) {
            MemoryStream MS = new MemoryStream();
            photo.Save(MS, System.Drawing.Imaging.ImageFormat.Jpeg);
            byte[] Imagebytes = MS.ToArray();
            MS.Dispose();

            //Set up basic variables for constructing the multipart/form-data data
            string newline = "\r\n";
            string boundary = DateTime.Now.Ticks.ToString("x");
            string data = "";

            //Construct data
            data += "--" + boundary + newline;
            data += "Content-Disposition: form-data; name=\"message\"" + newline + newline;
            data += message + newline;

            data += "--" + boundary + newline;
            data += "Content-Disposition: form-data; filename=\"test.jpg\"" + newline;
            data += "Content-Type: image/jpeg" + newline + newline;

            string ending = newline + "--" + boundary + "--" + newline;

            //Convert data to byte[] array
            MemoryStream finaldatastream = new MemoryStream();
            byte[] databytes = Encoding.UTF8.GetBytes(data);
            byte[] endingbytes = Encoding.UTF8.GetBytes(ending);
            finaldatastream.Write(databytes, 0, databytes.Length);
            finaldatastream.Write(Imagebytes, 0, Imagebytes.Length);
            finaldatastream.Write(endingbytes, 0, endingbytes.Length);
            byte[] finaldatabytes = finaldatastream.ToArray();
            finaldatastream.Dispose();

            //Make the request
            using (WebClient client = new WebClient()) {
                client.Headers.Add("Content-Type", "multipart/form-data; boundary=" + boundary);
                client.UploadDataAsync(new Uri(string.Format("https://graph.facebook.com/{0}/photos?access_token={1}", AlbumID, AccessToken)), "POST", finaldatabytes);
            }
        }
        #endregion

        #region Note
        /// <summary>
        /// Creates a new Note on Facebook. The method returns tne Note ID of the Note created.
        /// </summary>
        /// <param name="Title">Title of the Note</param>
        /// <param name="Message">Content of the Note</param>
        /// <returns>Note ID</returns>
        public string CreateNote(string Title, string Message)
        {
            Dictionary<string, string> parameters = new Dictionary<string, string>();
            parameters.Add("access_token", AccessToken);
            parameters.Add("subject", Title);
            parameters.Add("message", Message);
            using (WebClient WC = new WebClient()) {
                string _Response = WebResponseHelper.UploadString("https://graph.facebook.com/me/notes", Helpers.Common.BuildQueryString(parameters));
                JsonObject JO = new JsonObject(_Response);
                return JO["id"].ToString();
            }
        }
        #endregion

        /// <summary>
        /// Mark a Notification as read
        /// </summary>
        /// <param name="NotificationID">Notification ID</param>
        public void MarkNotificationAsRead(string NotificationID) {
            WebResponseHelper.UploadString(string.Format("https://graph.facebook.com/{0}?unread=0", NotificationID), string.Format("access_token={0}", AccessToken));
        }

        #endregion

        #region special
        /// <summary>
        /// Clear the cache which Facebook Graph Toolkit is using
        /// </summary>
        public static void ClearCache() {
            List<string> keys = new List<string>();
            IDictionaryEnumerator enumerator = HttpContext.Current.Cache.GetEnumerator();
            while (enumerator.MoveNext()) {
                keys.Add(enumerator.Key.ToString());
            }
            foreach (string key in keys) {
                if (key.StartsWith("FacebookGraph")) HttpContext.Current.Cache.Remove(key);
            }
        }
        /// <summary>
        /// Use a Graph Api connection to test whether the Access Token is valid
        /// </summary>
        /// <param name="AccessToken">Access Token to be tested</param>
        /// <returns>The result of the Access Token validation test</returns>
        public static AccessTokenValidationResult ValidateAccessToken(string AccessToken) {
            string R;
            WebClient WC = new WebClient();
            try {
                R = WC.DownloadString("https://graph.facebook.com/me?access_token=" + AccessToken);
                return AccessTokenValidationResult.Success;
            }
            catch (WebException WebEx) {
                Stream S = WebEx.Response.GetResponseStream();
                using (StreamReader SR = new StreamReader(S)) {
                    R = SR.ReadToEnd();
                    JsonObject JO = new JsonObject(R);
                    JsonObject ErrorJO = (JsonObject)JO["error"];
                    if (ErrorJO["message"].ToString() == "Error validating access token: The session has been invalidated because the user has changed the password.") {
                        return AccessTokenValidationResult.ChangedPassword;
                    }
                    if (ErrorJO["message"].ToString() == "Error validating access token: The session is invalid because the user logged out.") {
                        return AccessTokenValidationResult.LoggedOut;
                    }
                    if (ErrorJO["message"].ToString().Contains("has not authorized application")) {
                        return AccessTokenValidationResult.NotAuthorized;
                    }
                    if (ErrorJO["message"].ToString().Contains("Session has expired")) return AccessTokenValidationResult.Expired;
                    return AccessTokenValidationResult.UnknownError;
                }
            }
        }
        #endregion
    }
    /// <summary>
    /// Represents the result of an Access Token Validation Test
    /// </summary>
    public enum AccessTokenValidationResult{
        /// <summary>
        /// The Access Token is valid and can be used
        /// </summary>
        Success,
        /// <summary>
        /// The Access Token is invalid because the user changed the his or her password.
        /// </summary>
        ChangedPassword,
        /// <summary>
        /// The Access Token is invalid because the user has not authorized or has removed the Application.
        /// </summary>
        NotAuthorized,
        /// <summary>
        /// The Access Token is invalid because the user has logged out.
        /// </summary>
        LoggedOut,
        /// <summary>
        /// The Access Token is invalid because it has expired.
        /// </summary>
        Expired,
        /// <summary>
        /// It appears that the Access Token is invalid, but the exact cause is indetermined.
        /// </summary>
        UnknownError
    }
}
